package main

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"time"

	"happy-sudoku/graphql"
	"happy-sudoku/graphql/resolvers"
	"happy-sudoku/internal/config"
	"happy-sudoku/internal/manager"
	"happy-sudoku/internal/middleware"
	"happy-sudoku/internal/model"
	"happy-sudoku/internal/repository"
	"happy-sudoku/internal/service"
	"happy-sudoku/internal/util"

	"github.com/99designs/gqlgen/handler"
	"github.com/alicebob/miniredis"
	"github.com/go-chi/chi"

	chimid "github.com/go-chi/chi/middleware"
	"github.com/go-redis/redis"
	"github.com/jinzhu/gorm"
	"github.com/mattn/go-sqlite3"
	"github.com/rs/cors"
	"github.com/sirupsen/logrus"
)

func initDb() (*redis.Client, *gorm.DB, error) {
	logrus.Info("Initializing DB")
	mr, err := miniredis.Run()
	if err != nil {
		return nil, nil, err
	}
	logrus.Info("miniredis started at ", mr.Addr())
	_ = sqlite3.ErrNoMask
	// var db *gorm.DB
	// db, err := gorm.Open("sqlite3", ":memory:")
	db, err := gorm.Open("sqlite3", "db.sqlite")
	logrus.Info("sqlite3 started")
	if err != nil {
		return nil, nil, err
	}

	rdb := redis.NewClient(&redis.Options{
		Addr: mr.Addr(),
	})
	logrus.Info("redis connected")

	return rdb, db, nil
}

func initTable(db *gorm.DB) {
	db.AutoMigrate(&model.Player{})
	db.AutoMigrate(&model.CharacterData{})
	db.AutoMigrate(&model.Puzzle{})
}

func main() {
	serverCfg, err := config.ParseConfig()
	if err != nil {
		panic(err)
	}
	{
		cfgStr, err := serverCfg.Marshal()
		if err != nil {
			panic(err)
		}
		fmt.Println("Use Config")
		fmt.Println(cfgStr)
	}

	if serverCfg.Server.Debug {
		logrus.SetLevel(logrus.DebugLevel)
	} else {
		logrus.SetLevel(logrus.InfoLevel)
	}
	rdb, db, err := initDb()
	if err != nil {
		logrus.WithError(err).Error("init db failed")
		return
	}
	defer rdb.Close()
	defer db.Close()

	initTable(db)
	logrus.Info("Init db successfully")
	// Init Secret
	if serverCfg.Server.RandomSecret {
		serverCfg.Server.Secret = util.NewUUIDString()
	}
	// repo
	playerRepo := repository.NewPlayerRepo(db)
	characterRepo := repository.NewCharacterDataRepo(db)
	playerStateRepo := repository.NewPlayerStateRepo(rdb)
	roomRepo := repository.NewRoomRepo(rdb)
	puzzleRepo := repository.NewPuzzleRepo(db)
	authRepo := repository.NewAuthRepo(rdb, db)
	// mgr
	playerMgr := manager.NewPlayerManager(playerRepo, playerStateRepo)
	puzzleMgr := manager.NewPuzzleManager(puzzleRepo)
	roomSyncMgr := manager.NewRoomSyncManager()
	roomMgr := manager.NewRoomManager(manager.RoomManagerCfg{
		RoomRepo:    roomRepo,
		PuzzleMgr:   puzzleMgr,
		PlayerMgr:   playerMgr,
		RoomSyncMgr: roomSyncMgr,
	})
	// service
	playerSrv := service.NewPlayerService(service.PlayerServiceConfig{
		PlayerRepo: playerRepo,
		AuthRepo:   authRepo,
		CharRepo:   characterRepo,
		Mgr:        playerMgr,
		RoomMgr:    roomMgr,
	})
	authSrv := service.NewAuthJwtService([]byte(serverCfg.Server.Secret))
	roomSrv := service.NewRoomService(service.RoomServiceCfg{
		RoomRepo:    roomRepo,
		PuzzleMgr:   puzzleMgr,
		PlayerMgr:   playerMgr,
		RoomSyncMgr: roomSyncMgr,
		RoomMgr:     roomMgr,
	})
	puzzleSrv := service.NewPuzzleService(puzzleMgr)

	resolverCfg := resolvers.ResolverConfig{
		PlayerSrv: playerSrv,
		AuthSrv:   authSrv,
		RoomSrv:   roomSrv,
		PuzzleSrv: puzzleSrv,
	}

	graphqlHandler, err := graphql.NewHandler(resolverCfg, graphql.Directives())
	if err != nil {
		logrus.WithError(err).Error("Start graphql failed")
		return
	}

	router := chi.NewRouter()
	router.Use(cors.New(cors.Options{
		AllowedOrigins:   serverCfg.Server.AllowOrigins,
		AllowedHeaders:   []string{"authorization", "content-type"},
		AllowCredentials: true,
		Debug:            serverCfg.Server.Debug,
	}).Handler)
	// router.Use(chimid.Logger)
	router.Use(chimid.RequestID)

	authHandler := middleware.AuthMiddleware(authSrv)
	router.Use(authHandler)
	router.Handle("/graphql", graphqlHandler)
	playgroundHandler := handler.Playground("GraphQL", "/graphql")
	router.Handle("/playground", playgroundHandler)

	startAppHandler(serverCfg.Server.Port, router)
}

func startAppHandler(appPort int, appHandler http.Handler) {
	logrus.Infof("start server at port: %d", appPort)
	appSrv := startServerInstance(appPort, appHandler)

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	<-c
	logrus.Info("interrupt by signal, shutting down")
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	err := appSrv.Shutdown(ctx)
	if err != nil {
		logrus.WithError(err).Error("shutting down but with error")
		os.Exit(1)
	}
	logrus.Info("shutdown")
	os.Exit(0)
}

func startServerInstance(port int, handler http.Handler) *http.Server {
	srv := &http.Server{
		Addr:         fmt.Sprintf(":%d", port),
		WriteTimeout: time.Second * 15,
		ReadTimeout:  time.Second * 15,
		IdleTimeout:  time.Second * 60,
		Handler:      handler,
	}
	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logrus.Error(err)
		}
	}()
	return srv
}
